package app.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.text.SimpleDateFormat;
import java.util.LinkedList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JScrollBar;

import app.Main;
import app.gui.RecordsModel.OnOff;
import app.gui.RecordsModel.Record;
import app.util.LI;

public class RecordsDiagram extends JPanel implements MouseListener, MouseMotionListener {

	//  bar heights ratio
	private static final double[] HEIGHT_RATIO = {
		0.09,
		0.27,
		0.27,
		0.27,
		0.05,
		0.05
	};

	private RecordsModel model;

	// last height before resizing
	private int lastWidth;
	private int lastHeight;

	// show cursor line in chart panel
	private boolean showCursor;
	private int cursorXPos;

	private int headerWidth = 100;
	private int valueWidth = 100;

	private JScrollBar scroll;
	private JPanel headerPanel;
	private JPanel chartPanel;
	private JPanel valuePanel;

	private List<IBar> bars;


	/**
	 * Constructor
	 * 
	 */
	public RecordsDiagram(RecordsModel model) {
		super(new BorderLayout());
		this.model = model;
		
		bars = new LinkedList<IBar>();

		bars.add(new TimeBar("Čas"));
		bars.add(new ChartBar("Teplota", 0, 50, "°C"));
		bars.add(new ChartBar("Vlhkost", 0, 100, "%"));
		bars.add(new ChartBar("Půdní vlhkost", 0, 100, "%"));
		bars.add(new OnOffBar("Zalévání"));
		bars.add(new OnOffBar("Větrání"));

		headerPanel = new HeaderPanel();
		headerPanel.setPreferredSize(new Dimension(headerWidth, 0));
		headerPanel.setBorder(BorderFactory.createLineBorder(Color.black, 1));

		chartPanel = new ChartPanel();
		chartPanel.setPreferredSize(new Dimension(model.getWidth(), 0));
		chartPanel.setBorder(BorderFactory.createLineBorder(Color.black, 1));

		valuePanel = new ValuePanel();
		valuePanel.setPreferredSize(new Dimension(valueWidth, 0));
		valuePanel.setBorder(BorderFactory.createLineBorder(Color.black, 1));


		scroll = new JScrollBar(JScrollBar.HORIZONTAL, 0, 1, 0, 1);
		scroll.setUnitIncrement(1);
		scroll.addAdjustmentListener(new ScrollbarListener());
		add(headerPanel, BorderLayout.WEST);
		add(chartPanel, BorderLayout.CENTER);
		add(valuePanel, BorderLayout.EAST);
		add(scroll, BorderLayout.SOUTH);

		addMouseListener(this);
		addMouseMotionListener(this);

		setSize(new Dimension(800, 400));
		setPreferredSize(new Dimension(800, 400));

		refresh();
	}

	@Override
	public void paint(Graphics g) {
		int height = getHeight() - scroll.getHeight();
		if(lastWidth != getWidth() || lastHeight != height) {
			refresh();
		}		

		super.paint(g);

	}

	/**
	 * Refresh Diagram panel
	 * 
	 */
	public void refresh() {
		// recount dimensions
		resize();

		// set scroll bar max value
		scroll.setMaximum(model.getWidth() - chartPanel.getWidth());
		// set Temperature chart
		((ChartBar) bars.get(1)).setData(model.getTemperature());
		// set Humidity chart
		((ChartBar) bars.get(2)).setData(model.getHumidity());
		// set Soil Moisture chart
		((ChartBar) bars.get(3)).setData(model.getMoisture());
		// set Watering chart
		((OnOffBar) bars.get(4)).setData(model.getWatering());
		// set Ventilation chart
		((OnOffBar) bars.get(5)).setData(model.getVentilation());

		repaint();
	}

	/**
	 * Recount position and dimension by all bars after resize
	 * 
	 */
	public void resize() {
		lastWidth = getWidth();
		lastHeight = getHeight() - scroll.getHeight();
		int y = 0;

		for(int i = 0; i < bars.size(); i++) {
			bars.get(i).setX(scroll.getValue());
			bars.get(i).setY(y);
			bars.get(i).setWidth(chartPanel.getWidth());
			bars.get(i).setHeight((int)(lastHeight * HEIGHT_RATIO[i]));
			y += bars.get(i).getHeight();
		}
		((TimeBar) bars.get(0)).setTimeline(model.getTotalTime(scroll.getValue()), 
				model.getTotalTime(scroll.getValue() + chartPanel.getWidth()), 
				(int) RecordsModel.TIMESTEP, getHeight());
		repaint();
	}

	/**
	 * Set data to all bars when scroll bar is changed
	 * 
	 */
	public void scrolled() {
		for(int i = 0; i < bars.size(); i++) {
			bars.get(i).setX(scroll.getValue());
		}
		((TimeBar) bars.get(0)).setTimeline(model.getTotalTime(scroll.getValue()), 
				model.getTotalTime(scroll.getValue() + chartPanel.getWidth()), 
				(int) RecordsModel.TIMESTEP, getHeight());

		repaint();
	}

	/**
	 * Draw bars borders
	 * 
	 * @param g Graphics of panel
	 * @param width Width of panel
	 * @param heightOffset Offset added to bar heights
	 */
	public void drawLines(Graphics g, int width) {		

		for(IBar bar : bars) {
			g.drawLine(0, bar.getY() + bar.getHeight(), width, bar.getY() + bar.getHeight());
		}
	}

	/**
	 * Draw bar titles
	 * 
	 * @param g Graphics of panel
	 */
	public void drawText(Graphics g) {
		Font f = new Font("Dialog", Font.PLAIN, 13);
		g.setFont(f);
		for(IBar bar : bars) {
			g.drawString(bar.getTitle(), 5, bar.getY() + bar.getHeight() - 1);
		}
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		if(e.getX() > headerPanel.getWidth() && 
				e.getX() < (this.getWidth() - valuePanel.getWidth())) {
			showCursor = true;
			cursorXPos = e.getX() - headerPanel.getWidth();
			repaint();
		}
		else {
			if(showCursor) {
				showCursor = false;
				repaint();
			}

		}


	}

	@Override
	public void mouseClicked(MouseEvent e) {}

	@Override
	public void mouseEntered(MouseEvent e) {}

	@Override
	public void mouseExited(MouseEvent e) {
		if(showCursor) {
			showCursor = false;
			repaint();
		}
	}

	@Override
	public void mouseDragged(MouseEvent e) {}

	@Override
	public void mousePressed(MouseEvent e) {}

	@Override
	public void mouseReleased(MouseEvent e) {}


	/**
	 * 
	 * 
	 * @author Lukas Vavra
	 *
	 */
	private class HeaderPanel extends JPanel {

		public HeaderPanel() {
		}

		public void paint(Graphics g) {
			super.paint(g);

			drawLines(g, getWidth());

			// draw header text
			drawText(g);
		}
	}

	private class ChartPanel extends JPanel {
		public ChartPanel() {
			setBackground(Color.white);
		}

		public void paint(Graphics g) {
			super.paint(g);
			drawLines(g, getWidth());
			if(showCursor) {
				g.setColor(Color.red);
				g.drawLine(cursorXPos, 0, cursorXPos, getHeight());
			}

			if(!bars.isEmpty()) {
				for(IBar bar : bars) {
					bar.draw(g);
				}
			}
		}

	}

	private class ValuePanel extends JPanel {

		public ValuePanel() {

		}

		public void paint(Graphics g) {
			super.paint(g);
			drawLines(g, getWidth());

			// draw values, if cursor is visible
			if(showCursor) {
				for(IBar bar : bars) {
					bar.drawValue(g, model.getTotalTime(cursorXPos + scroll.getValue()));
				}
			}
		}
	}

	/**
	 * Time bar, contains time data
	 * type of IBar
	 * 
	 * @author Lukas Vavra
	 *
	 */
	private class TimeBar implements IBar {		
		public static final int FONT_HEIGHT = 13;

		private String title;
		private int width;
		private int height;
		private int y;
		private int x;
		private long minTime;
		private long maxTime;
		private int timeStep;
		private int lineLength;

		Font font = new Font("Dialog", Font.PLAIN, FONT_HEIGHT);
		SimpleDateFormat formatDate = new SimpleDateFormat("dd.MM.yyyy");
		SimpleDateFormat formatTime = new SimpleDateFormat("HH:mm:ss");		

		/**
		 * Constructor
		 * 
		 * @param title Bar title
		 */
		public TimeBar(String title) {
			this.title = title;
		}

		public void setTimeline(long minTime, long maxTime, int timeStep, int lineLength) {
			this.minTime = minTime;
			this.maxTime = maxTime;
			this.timeStep = timeStep;
			this.lineLength = lineLength;
		}

		/** 
		 * Draw time line
		 */
		public void draw(Graphics g) {
			long drawTime = ((minTime / timeStep)) * timeStep;
			int x;

			g.setColor(Color.black);
			while(drawTime < maxTime) {
				x = (int) LI.interpolate(minTime, maxTime, 0, width, drawTime);
				g.drawLine(x, 0, x, lineLength);
				g.drawString(formatDate.format(drawTime * 1000), x + 1, y + height);
				g.drawString(formatTime.format(drawTime * 1000), x + 1, y + height - (FONT_HEIGHT + 1));
				drawTime += timeStep;
			}
		}

		/**
		 * Draw value of cursor
		 */
		public void drawValue(Graphics g, long time) {
			g.setFont(font);

			g.drawString(formatDate.format(time * 1000), 5, y + height);
			g.drawString(formatTime.format(time * 1000), 5, y + height - (FONT_HEIGHT + 1));

		}


		public String getTitle() {
			return title;
		}

		public int getWidth() {
			return width;
		}

		public void setWidth(int width) {
			this.width = width;
		}

		public int getHeight() {
			return height;
		}

		public void setHeight(int height) {
			this.height = height;
		}

		public int getY() {
			return y;
		}

		public void setY(int y) {
			this.y = y;
		}

		@Override
		public long getX() {
			return x;
		}

		@Override
		public void setX(int x) {
			this.x = x;			
		}
	}

	/**
	 * Bar with recorded data, showing it in chart
	 * type of IBar
	 * 
	 * @author Lukas Vavra
	 *
	 */
	private class ChartBar implements IBar {
		private String title;
		private int width;
		private int height;
		private int x; // scroll bar value
		private int y;

		private int minValue;
		private int maxValue;

		private Point[] points;
		private List<Record> data;

		private String valuePostFix;

		Font font = new Font("Dialog", Font.PLAIN, 13);
		/**
		 * Constructor
		 * 
		 * @param title Bar title
		 */
		public ChartBar(String title) {
			this.setTitle(title);
		}

		public ChartBar(String title, int minValue, int maxValue, String valuePostFix) {
			this.setTitle(title);
			this.setMinValue(minValue);
			this.setMaxValue(maxValue);
			this.setValuePostFix(valuePostFix);
		}

		public void setData(List<Record> data) {
			this.data = data;
			points = new Point[data.size()];
			int px, py;

			for(int i = 0; i < data.size(); i++) {
				px = model.getTotalX(data.get(i).getTime());
				py = (int) (y + LI.interpolate(getMinValue(), getMaxValue(), height - 1, 1, data.get(i).getValue()));
				points[i] = new Point(px, py); 
			}
		}

		@Override
		public void draw(Graphics g) {

			if(points.length > 1) {
				int i = 0;
				while(i < points.length && points[i].x < this.x) {
					i++;
				}
				g.setColor(Color.blue);
				if(i < points.length && i > 0) {
					g.drawLine(points[i - 1].x - this.x, points[i - 1].y, 
							points[i].x - this.x, points[i].y);
				}
				i++;
				while(i < points.length && points[i].x < this.x + width) {
					g.drawLine(points[i - 1].x - this.x, points[i - 1].y, 
							points[i].x - this.x, points[i].y);
					i++;
				}
				if(i < points.length) {
					g.drawLine(points[i - 1].x - this.x, points[i - 1].y, 
							points[i].x - this.x, points[i].y);
				}
			}
		}

		public void drawValue(Graphics g, long time) {
			g.drawString(Integer.toString(getValue(time)).concat(" ").concat(valuePostFix), 5, y + height - 1);

		}
		
		private int getValue(long time) {
			int i = 0;
			while(i < data.size() && data.get(i).before(time)) {
				i++;
			}
			if(i > 0 && i < data.size()) {
				if(time == data.get(i).getTime()) {
					return data.get(i).getValue();
				}
				else {
					return (int) LI.interpolate(data.get(i - 1).getTime(), data.get(i).getTime(), 
							data.get(i - 1).getValue(), data.get(i).getValue(), 
							time);
				}
			}
			return 0;
		}

		@Override
		public String getTitle() {
			return title;
		}

		@Override
		public int getWidth() {
			return width;
		}

		@Override
		public int getHeight() {
			return height;
		}

		@Override
		public void setWidth(int width) {
			this.width = width;
		}

		@Override
		public void setHeight(int height) {
			this.height = height;
		}

		@Override
		public int getY() {
			return y;
		}

		@Override
		public void setY(int y) {
			this.y = y;
		}

		@Override
		public long getX() {
			return x;
		}

		@Override
		public void setX(int x) {
			this.x = x;	
		}


		public int getMinValue() {
			return minValue;
		}

		public void setMinValue(int minValue) {
			this.minValue = minValue;
		}

		public int getMaxValue() {
			return maxValue;
		}

		public void setMaxValue(int maxValue) {
			this.maxValue = maxValue;
		}

		public String getValuePostFix() {
			return valuePostFix;
		}

		public void setValuePostFix(String valuePostFix) {
			this.valuePostFix = valuePostFix;
		}

		public void setTitle(String title) {
			this.title = title;
		}
	}


	/**
	 * Bar with On - Off states of device
	 * type of IBar
	 * 
	 * @author Lukas Vavra
	 *
	 */
	private class OnOffBar implements IBar {
		private String title;
		private int width;
		private int height;
		private int x;
		private int y;

		// OnOff items
		private List<OnOff> items;

		/**
		 * Constructor
		 * 
		 * @param title Bar title
		 */
		public OnOffBar(String title) {
			this.title = title;
		}

		public void setData(List<OnOff> data) {

		}

		@Override
		public String getTitle() {
			return title;
		}

		@Override
		public int getWidth() {
			return width;
		}

		@Override
		public int getHeight() {
			return height;
		}


		@Override
		public void setWidth(int width) {
			this.width = width;
		}

		@Override
		public void setHeight(int height) {
			this.height = height;
		}

		@Override
		public int getY() {
			return y;
		}

		@Override
		public void setY(int y) {
			this.y = y;			
		}

		@Override
		public long getX() {
			return x;
		}

		@Override
		public void setX(int x) {
			this.x = x;
		}

		@Override
		public void draw(Graphics g) {
			// TODO Automaticky generovaný stub metody

		}

		public void drawValue(Graphics g, long time) {
			Font f = new Font("Dialog", Font.PLAIN, 13);
			g.setFont(f);

		}
	}

	/**
	 * Horizontal Scroll Bar listener
	 * 
	 * @author Lukas Vavra
	 *
	 */
	private class ScrollbarListener implements AdjustmentListener {

		@Override
		public void adjustmentValueChanged(AdjustmentEvent e) {
			scrolled();
		}

	}
}
